import asyncio
from tornado.websocket import WebSocketClosedError

from webutils.compat import complex_json
from pubsub.core import App
from webutils.layers.managers import JsonMixin

from . import const


class BrokerApp(App):
    """
    中控使用的消息广播服务

    >>> room_id = 123
    >>> user_id = 234
    >>> # 需要订阅的频道
    >>> channels = {'group': 'teacher'. 'user_id': user_id}
    >>> app = BrokerApp(room_id=room_id, channels_subscribed=channels)
    >>> # 启动订阅
    >>> app.start_consume(websocket)
    >>> # 发送任务到教师频道
    >>> app.send_task('chat', None, group='teacher')
    """

    def __init__(self,
                 *args,
                 room_id=None,
                 channels_subscribed=None,
                 **kwargs):
        assert room_id is not None
        self.room_id = room_id
        channels = self._process_channels(channels_subscribed)
        channels['room_id'] = self.room_id
        kwargs['channels_subscribed'] = channels
        super().__init__(*args, **kwargs)

    def _process_channels(self, channels):
        if not channels:
            return channels
        meta_headers = {'x-match', 'room_id'}
        if 'group' in channels:
            group = channels['group']
            group = const.UserGroup(group)
            if group is const.UserGroup.ALL:
                channels.pop('group')
                channels['room_id'] = self.room_id
            else:
                channels['group'] = group.value
        for key, value in channels.items():
            if key in meta_headers:
                continue
            channels[key] = f'{self.room_id}-{value}'
        return channels

    async def send_task(self, *args, force=False, **channel_kwargs):
        if not self.websocket.is_live and not force:
            self.logger.info('[send_task] try to send task while '
                             'websocket is closed')
            return
        if self.broker.is_closed and not force:
            self.logger.info('[send_task] broker is already closed')
            return
        channel_kwargs = self._process_channels(channel_kwargs)
        try:
            return await super().send_task(*args, **channel_kwargs)
        except asyncio.exceptions.CancelledError as err:
            raise Exception(err)

    async def start_consume(self, websocket):
        self.websocket = websocket
        await super().start_consume()

    async def default_exception_handler(self, task_name, err):
        if isinstance(err, WebSocketClosedError):
            self.logger.warning('process task %s while websocket is closed',
                                task_name)
            return
        return await super().default_exception_handler(task_name, err)

    async def execute_task(self, task_handler, task_arguments, task_meta):
        if not self.websocket.is_live:
            self.logger.info('websocket is closed, discard message: %s',
                             task_handler)
            return
        await task_handler(self.websocket, task_arguments, task_meta)


class CheckInBroadcaster(App, JsonMixin):
    async def consume_task(self, raw_task_data):
        self.ws.logger.info('receive check in data: %s', raw_task_data)
        data = self.deserialize(raw_task_data)
        data = complex_json.dumps(data)
        await self.ws.write_message(data)

    async def start_consume(self, ws):
        self.ws = ws
        assert self.channels_subscribed is not None
        assert self.tasks is not None
        await self.broker.start_consume(
            self.channels_subscribed,
            self.consume_task,
        )


class CheckInLlist:
    def __init__(self,
                 broker,
                 tasks=None,
                 task_meta=None,
                 channels_subscribed=None,
                 exception_handler=None,
                 **kwargs):
        self.broker = broker
        self.channels_subscribed = channels_subscribed
        self.task_meta = task_meta
        self.tasks = tasks
        self.exception_handler = exception_handler
        self.wsdata = []
        self.while_status = True

    @property
    def logger(self):
        return self.broker.logger

    async def start_publish(self):
        await self.broker.start_publish()
        self.logger.debug('ready to publish')

    async def execute(self, command, *args):
        connection = await self.broker.get_connection()
        self.logger.debug(f'[{connection}] execute [{command}] [{args}]')
        return await connection.execute(command, *args)

    async def start_consume(self, channel):
        subkey = channel + ':sub'
        while self.while_status:
            len_ = await self.execute('LLEN', channel)
            if len_ == 0 and self.wsdata:
                text = {"errcode": 0, "errmsg": "ok",
                        "data": {"total": len(self.wsdata),
                                 "list": []}}
                for i in self.wsdata:
                    text['data']['list'].append(i)
                text_json = complex_json.dumps(text)
                await self.execute('PUBLISH', subkey, text_json)
                self.wsdata = []
                continue
            res = await self.execute('BLPOP', channel, 15)
            if res:
                if str(res) != 'userdata: NULL':
                    text_one = complex_json.loads(res[1])
                    self.wsdata.append(text_one)
